package storage

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/aliyun/aliyun-oss-go-sdk/oss"
)

// OSSConfig 阿里云OSS存储配置
type OSSConfig struct {
	AccessKeyID     string `yaml:"access_key_id"`     // 访问密钥ID
	AccessKeySecret string `yaml:"access_key_secret"` // 访问密钥密钥
	Endpoint        string `yaml:"endpoint"`          // OSS服务的访问域名
	Bucket          string `yaml:"bucket"`            // 存储空间名称
	BaseURL         string `yaml:"base_url"`          // 自定义域名（可选）
	PathPrefix      string `yaml:"path_prefix"`       // 路径前缀（可选）
	IsPublic        bool   `yaml:"is_public"`         // 是否公开访问
}

// GetStorageType 获取存储类型
func (c *OSSConfig) GetStorageType() StorageType {
	return StorageTypeOSS
}

// OSSStorage 阿里云OSS存储实现
type OSSStorage struct {
	config     *OSSConfig
	client     *oss.Client
	bucket     *oss.Bucket
	pathPrefix string
}

// NewOSSStorage 创建阿里云OSS存储实例
func NewOSSStorage(config *OSSConfig) (*OSSStorage, error) {
	if config == nil {
		return nil, errors.New("OSS存储配置不能为空")
	}

	if config.AccessKeyID == "" || config.AccessKeySecret == "" {
		return nil, errors.New("OSS访问密钥不能为空")
	}

	if config.Endpoint == "" {
		return nil, errors.New("OSS服务的访问域名不能为空")
	}

	if config.Bucket == "" {
		return nil, errors.New("OSS存储空间名称不能为空")
	}

	// 创建OSS客户端
	client, err := oss.New(config.Endpoint, config.AccessKeyID, config.AccessKeySecret)
	if err != nil {
		return nil, fmt.Errorf("创建OSS客户端失败: %w", err)
	}

	// 获取存储空间
	bucket, err := client.Bucket(config.Bucket)
	if err != nil {
		return nil, fmt.Errorf("获取OSS存储空间失败: %w", err)
	}

	// 规范化路径前缀
	pathPrefix := strings.TrimPrefix(config.PathPrefix, "/")
	if pathPrefix != "" && !strings.HasSuffix(pathPrefix, "/") {
		pathPrefix += "/"
	}

	return &OSSStorage{
		config:     config,
		client:     client,
		bucket:     bucket,
		pathPrefix: pathPrefix,
	}, nil
}

// Upload 上传文件
func (s *OSSStorage) Upload(ctx context.Context, name string, reader io.Reader, option *UploadOption) (*FileInfo, error) {
	if option == nil {
		option = &UploadOption{}
	}

	// 确定存储路径
	path := name
	if option.Path != "" {
		path = filepath.Join(option.Path, name)
	}

	// 替换Windows路径分隔符为URL路径分隔符
	path = filepath.ToSlash(path)

	// 添加路径前缀
	objectKey := s.pathPrefix + path

	// 设置对象属性
	objectOptions := []oss.Option{}

	// 设置内容类型
	contentType := option.ContentType
	if contentType == "" {
		contentType = getContentTypeByExt(filepath.Ext(name))
	}
	objectOptions = append(objectOptions, oss.ContentType(contentType))

	// 设置元数据
	if option.Metadata != nil {
		for k, v := range option.Metadata {
			objectOptions = append(objectOptions, oss.Meta(k, v))
		}
	}

	// 设置访问权限
	if option.IsPublic {
		objectOptions = append(objectOptions, oss.ObjectACL(oss.ACLPublicRead))
	} else {
		objectOptions = append(objectOptions, oss.ObjectACL(oss.ACLPrivate))
	}

	// 上传文件
	err := s.bucket.PutObject(objectKey, reader, objectOptions...)
	if err != nil {
		return nil, fmt.Errorf("上传文件到OSS失败: %w", err)
	}

	// 获取文件信息
	objectMeta, err := s.bucket.GetObjectDetailedMeta(objectKey)
	if err != nil {
		return nil, fmt.Errorf("获取OSS对象元数据失败: %w", err)
	}

	// 获取文件大小
	size, _ := strconv.ParseInt(objectMeta.Get("Content-Length"), 10, 64)

	// 获取最后修改时间
	lastModified, _ := http.ParseTime(objectMeta.Get("Last-Modified"))

	// 构建访问URL
	url, err := s.GetURL(ctx, path, option.Expiration)
	if err != nil {
		return nil, fmt.Errorf("获取文件访问URL失败: %w", err)
	}

	return &FileInfo{
		Name:         name,
		Path:         path,
		URL:          url,
		Size:         size,
		ContentType:  contentType,
		LastModified: lastModified,
	}, nil
}

// Delete 删除文件
func (s *OSSStorage) Delete(ctx context.Context, path string) error {
	// 替换Windows路径分隔符为URL路径分隔符
	path = filepath.ToSlash(path)

	// 添加路径前缀
	objectKey := s.pathPrefix + path

	// 删除文件
	err := s.bucket.DeleteObject(objectKey)
	if err != nil {
		return fmt.Errorf("删除OSS对象失败: %w", err)
	}

	return nil
}

// GetURL 获取文件访问URL
func (s *OSSStorage) GetURL(ctx context.Context, path string, expiration time.Duration) (string, error) {
	// 替换Windows路径分隔符为URL路径分隔符
	path = filepath.ToSlash(path)

	// 添加路径前缀
	objectKey := s.pathPrefix + path

	// 如果配置了自定义域名且文件是公开的，则直接返回自定义域名URL
	if s.config.BaseURL != "" && (s.config.IsPublic || expiration == 0) {
		baseURL := s.config.BaseURL
		if !strings.HasSuffix(baseURL, "/") {
			baseURL += "/"
		}
		return baseURL + objectKey, nil
	}

	// 如果需要临时URL
	if expiration > 0 {
		// 生成签名URL
		signedURL, err := s.bucket.SignURL(objectKey, oss.HTTPGet, int64(expiration.Seconds()))
		if err != nil {
			return "", fmt.Errorf("生成OSS签名URL失败: %w", err)
		}
		return signedURL, nil
	}

	// 默认使用OSS默认域名
	return fmt.Sprintf("https://%s.%s/%s", s.config.Bucket, s.config.Endpoint, objectKey), nil
}

// GetFileInfo 获取文件信息
func (s *OSSStorage) GetFileInfo(ctx context.Context, path string) (*FileInfo, error) {
	// 替换Windows路径分隔符为URL路径分隔符
	path = filepath.ToSlash(path)

	// 添加路径前缀
	objectKey := s.pathPrefix + path

	// 获取文件元数据
	objectMeta, err := s.bucket.GetObjectDetailedMeta(objectKey)
	if err != nil {
		return nil, fmt.Errorf("获取OSS对象元数据失败: %w", err)
	}

	// 获取文件名
	name := filepath.Base(path)

	// 获取内容类型
	contentType := objectMeta.Get("Content-Type")
	if contentType == "" {
		contentType = getContentTypeByExt(filepath.Ext(name))
	}

	// 获取文件大小
	size, _ := strconv.ParseInt(objectMeta.Get("Content-Length"), 10, 64)

	// 获取最后修改时间
	lastModified, _ := http.ParseTime(objectMeta.Get("Last-Modified"))

	// 构建访问URL
	url, err := s.GetURL(ctx, path, 0)
	if err != nil {
		return nil, fmt.Errorf("获取文件访问URL失败: %w", err)
	}

	return &FileInfo{
		Name:         name,
		Path:         path,
		URL:          url,
		Size:         size,
		ContentType:  contentType,
		LastModified: lastModified,
	}, nil
}

// ListFiles 列出指定目录下的文件
func (s *OSSStorage) ListFiles(ctx context.Context, prefix string) ([]*FileInfo, error) {
	// 替换Windows路径分隔符为URL路径分隔符
	prefix = filepath.ToSlash(prefix)

	// 添加路径前缀
	ossPrefix := s.pathPrefix + prefix
	if !strings.HasSuffix(ossPrefix, "/") {
		ossPrefix += "/"
	}

	// 列举文件
	marker := ""
	maxKeys := 1000
	var files []*FileInfo

	for {
		lsRes, err := s.bucket.ListObjects(oss.Marker(marker), oss.Prefix(ossPrefix), oss.MaxKeys(maxKeys))
		if err != nil {
			return nil, fmt.Errorf("列举OSS对象失败: %w", err)
		}

		for _, object := range lsRes.Objects {
			// 跳过目录本身
			if object.Key == ossPrefix {
				continue
			}

			// 计算相对路径（去除前缀）
			relPath := strings.TrimPrefix(object.Key, s.pathPrefix)

			// 获取文件名
			name := filepath.Base(relPath)

			// 获取内容类型
			contentType := object.Type
			if contentType == "" {
				contentType = getContentTypeByExt(filepath.Ext(name))
			}

			// 构建访问URL
			url, err := s.GetURL(ctx, relPath, 0)
			if err != nil {
				return nil, fmt.Errorf("获取文件访问URL失败: %w", err)
			}

			files = append(files, &FileInfo{
				Name:         name,
				Path:         relPath,
				URL:          url,
				Size:         object.Size,
				ContentType:  contentType,
				LastModified: object.LastModified,
			})
		}

		// 如果没有更多结果，则退出循环
		if !lsRes.IsTruncated {
			break
		}

		// 设置下一页的起始点
		marker = lsRes.NextMarker
	}

	return files, nil
}

// Type 获取存储类型
func (s *OSSStorage) Type() StorageType {
	return StorageTypeOSS
}
